Skip to content

cluster predicate - unidirectional graph reachability matrix #921

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

LebedevRI
Copy link
Contributor

@LebedevRI LebedevRI commented May 3, 2025

Questions/notes:
1. Is there any documentation whatsoever on how to add new tests? I'd like to add them, but looks like pytest doesn't test in-tree std, but the install dir
2. Is there an autoformatter?
3. I've only added enum versions. Should there only be int versions, and enum versions shall just dispatch tothose?
4. I'm not sure about the multiroot suffix, should it be anyroot?
5. Also, i'm not sure if cluster is the best name? Maybe constellations / strongly connected components (scc) may be more descriptive, i'm not sure.

I needed this some time back, and having this
would have saved me quite some time.

Essentially, this ended up consisting of two parts:

  1. reachable_multiroot() - a seemingly rather boringly straight-forward generalization of the reachable(), which, instead of enforcing that the subgraph is reachable from the root node (given by index, non-optional), and thus forcing there to be a single joint subgraph, instead takes (zero or more) roots as a mask. The rest of the logic is generalized accordingly.

  2. cluster() predicate. This one took quite a bit of rewrites to fully distill it to it's purest+simplest+fastest form. :) The (final) basic idea is to, basically, give each node a unique "color", and then set the nodes, linked together by selected edges, to the same color, thus ensuring that the nodes, that are not in any way connected, don't have the same "color", and vice versa.

    Implementation-wise, instead of color, for each node, we track the index of
    the disjoint subgraph to which this node belongs, and it turned out
    that tracking that by tracking the root node of each subgraph is
    the best approach (referred to as root). So there's 3 parts:

    1. Splitting disjoint subgraphs: we then defer to reachable_multiroot() to enfore that each disjoint subgraph actually has a root node, thus different disjoint subgraphs have different roots.
    2. Coalescing same-color subgraphs: nodes, that are linked together by selected edge, have the same root.
    3. And then we actually compute reachability matrix based on whether or not two nodes have same root (belong to the same disjoint subgraph)

    Now, this is controversial. I've decided to return the reachability
    matrix, NOT the subgraph index. While sparse data structure seems better,
    it immediately requires symmetry breaking constraint, which is tricky
    (i have written it.), and --all-solutions actually ends up being MUCH
    slower (O^3?) than just returning reachability matrix...
    The directed version would need to return the matrix,
    so this is also future-proofing for consistency sake

I have an exhaustive test harness for the cluster() (but not the rest), it would not have been possible to refine the predicate so much otherwise, please see https://github.com/LebedevRI/minizinc-graph-reachability-matrix-predicate.mzn/tree/proof-of-concept/unidirectional

Refs. #837

@LebedevRI LebedevRI force-pushed the cluster branch 4 times, most recently from eb36508 to c22082e Compare May 11, 2025 20:29
Questions/notes:
1. Is there an autoformatter?
2. I'm not sure about the `multiroot` suffix, should it be `anyroot`?
3. Also, is `cluster` the best name? Is `constellations`/`sccs` better?

I needed this some time back, and having this
would have saved me quite some time.

Essentially, this ended up consisting of two parts:
1. `reachable_multiroot()` - a seemingly rather boringly straight-forward
    generalization of the `reachable()`, which, instead of enforcing that
    the subgraph is reachable from _the_ root node (given by index,
    non-optional), and thus forcing there to be a single joint subgraph,
    instead takes (zero or more) roots as a mask.
    The rest of the logic is generalized accordingly.
2. `cluster()` predicate.
    This one took **_quite_** a bit of rewrites to fully distill it
    to it's purest+simplest+fastest form. :) The (final) basic idea is to,
    basically, give each node a unique "color", and then set the nodes,
    linked together by selected edges, to the same color,
    thus ensuring that the nodes, that are not in any way connected,
    don't have the same "color", and vice versa.

    Implementation-wise, instead of color, for each node, we track the index of
    the disjoint subgraph to which this node belongs, and it turned out
    that tracking that by tracking the root node of each subgraph is
    the best approach (referred to as root). So there's 3 parts:
    1. Splitting disjoint subgraphs: we then defer to `reachable_multiroot()`
       to enfore that each disjoint subgraph actually has a root node,
       thus different disjoint subgraphs have different roots.
    2. Coalescing same-color subgraphs: nodes, that are linked together
       by selected edge, have the same root.
    3. And then we actually compute reachability matrix based on whether or not
       two nodes have same root (belong to the same disjoint subgraph)

    **Now, this is controversial. I've decided to return the reachability
    matrix, *NOT* the subgraph index. While sparse data structure seems better,
    it immediately requires symmetry breaking constraint, which is tricky
    (i have written it.), and `--all-solutions` actually ends up being *MUCH*
    slower (`O^3`?) than just returning reachability matrix...
    The directed version would need to return the matrix,
    so this is also future-proofing for consistency sake**

I have an exhaustive test harness for the `cluster()` (but not the rest),
it would not have been possible to refine the predicate so much otherwise,
please see https://github.com/LebedevRI/minizinc-graph-reachability-matrix-predicate.mzn/tree/proof-of-concept/unidirectional
@LebedevRI
Copy link
Contributor Author

Ok, i think this is basically it. If something else is needed, please let me know.
If this won't go anywhere, at least this was fun.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant